/*

    tools23d - tools for converting images to 3D formats.
    Copyright (C) 2014  Dave Gorst

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/


/**
 * @file   image-fi.c
 * @brief  FreeImage-specific image processing functions.
 *
 * This file contains FreeImage specific image processing functions.
 */


#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include <FreeImage.h>
#include "tools23d.h"

#ifdef BUILD_BTIME
	#include "btime23d.h"
#else
	#include "photo23d.h"
#endif

#include "image-fi.h"
#include "image.h"

static FREE_IMAGE_FILTER my_filter;
static unsigned int current_image;
FIBITMAP ** images_list;


/**
Initialise FreeImage image processing backend.

*/

void T3D_InitialiseBackend (void)
{
	FreeImage_Initialise (TRUE);

	FreeImage_SetOutputMessage(T3D_ImageErrorHandler);

	current_image = 0;

	images_list = NULL;
}

/**
Shutdown FreeImage image processing backend.

*/

void T3D_ShutdownBackend (void)
{
    FreeImage_DeInitialise ();
}

/**
Set the current active scaling filter.

@param	new_filter	Desired new filter.
*/

void T3D_SetActiveFilter (Filters new_filter)
{
	switch (new_filter)
	{
		case Box :
			my_filter = FILTER_BOX;
			break;
		case BSpline :
			my_filter = FILTER_BSPLINE;
			break;
		case CatmullRom :
			my_filter = FILTER_CATMULLROM;
			break;
		case Lanczos3 :
			my_filter = FILTER_LANCZOS3;
			break;
		case Bicubic :
		default :
			my_filter = FILTER_BICUBIC;
			break;
	}
}

/**
Create a new target image we'll use to write our processed output to.

@param	image_count	Pointer to the index of the newly created image.
@param	width		Desired width of the new target image.
@param	height		Desired height of the new target image.
@param	depth		Desired bit depth of the new target image.
@return	Boolean indicating whether image was created successfully.
*/

BOOL T3D_CreateImage (unsigned int * image_count, unsigned int width, unsigned int height, unsigned int depth)
{
	FIBITMAP ** new_image;

	new_image = T3D_AddImageToList ();

	*new_image = FreeImage_Allocate
	(
		width,
		height,
		depth,
		FI_RGBA_RED_MASK,
		FI_RGBA_GREEN_MASK,
		FI_RGBA_BLUE_MASK
	);

	*image_count = current_image - 1;

	return (*new_image != NULL);
}

/**
Load an file into a newly created image.

@param	image_count	Pointer to the index of the newly created image.
@param	filename	Name of file to load.
@return	Boolean indicating whether image was created successfully.
*/

BOOL T3D_LoadImage (image_index * image_count, char * filename)
{
	FIBITMAP ** new_image;

	new_image = T3D_AddImageToList ();

	*new_image = FreeImage_Load
	(
		T3D_RetrievePictureType (filename),
		filename,
		0
	);

	*image_count = current_image - 1;

	return (*new_image != NULL);
}


/**
Convert a previously created image to a new colourscheme.

@param	image_number	Index of the image to convert.
@param	colour_scheme	Desired colourscheme to convert to.
@return	Boolean indicating whether image was converted successfully.
*/

BOOL T3D_ColourConvert (image_index * image_number, ColourScheme colour_scheme)
{
	/* TODO - add other colourschemes. */
	/* TODO - Check current colourscheme to make sure it's not the same as the new one. */
	/* TODO - Check new image has been created before returning new one & destroying old one. */

	unsigned int previous_image = *image_number;

	FIBITMAP ** new_image = T3D_AddImageToList();

	*new_image = FreeImage_ConvertToGreyscale (images_list [previous_image]);

	*image_number = current_image - 1;

	T3D_DestroyImage (previous_image);

	return (*new_image != NULL);
}

/**
Delete a previously created image.

@param	image_number	Index of the image to destroy.
*/

void T3D_DestroyImage (image_index image_number)
{
	if ((image_number < current_image) && (images_list [image_number] != NULL))
	{
		FreeImage_Unload (images_list [image_number]);
		images_list [image_number] = NULL;
	}
}

/**
Scale an image to a new size.

@param	image_number	Pointer to the index of the scaled image.
@param	new_width	Desired width after scaling.
@param	new_height	Desired height after scaling.
@return	Boolean indicating whether image was scaled successfully.
*/

BOOL T3D_ScaleImage (image_index * image_number, unsigned int new_width, unsigned int new_height)
{
	unsigned int previous_image = *image_number;

	FIBITMAP ** new_image = T3D_AddImageToList();

	*new_image = FreeImage_Rescale (images_list [previous_image], new_width, new_height, my_filter);

	*image_number = current_image - 1;

	T3D_DestroyImage (previous_image);

	return (*new_image != NULL);
}


/**
Crop an image to a new size.

@param	image_number	Pointer to the index of the cropped image.
@param	left	Left point in image we wish to crop from
@param	top		Top point in image we wish to crop from
@param	width	Take this number of pixels rightwards.
@param	height	Take this number of pixels downwards.
@return	Boolean indicating whether image was scaled successfully.
*/

BOOL T3D_CropImage (image_index * image_number, unsigned int left, unsigned int top, unsigned int width, unsigned int height)
{
	unsigned int previous_image = *image_number;

	FIBITMAP ** new_image = T3D_AddImageToList();

	*new_image = FreeImage_Copy (images_list [previous_image], left, top, left + width, top + height);

	*image_number = current_image - 1;

	T3D_DestroyImage (previous_image);

	return (*new_image != NULL);
}

/**
@brief	Paste an image into a larger target image.

@param	source_image	Index of image to read from.
@param	destination_image	Index of image to write to.
@param	target_left     Left-hand edge of targetted area in destination.
@param	target_top     Top edge of targetted area in destination.
@return	::TRUE if successful.
*/

BOOL T3D_CopyInto (image_index source_image, image_index destination_image, unsigned int target_left, unsigned int target_top)
{
	return FreeImage_Paste
	(
		images_list [destination_image],
		images_list [source_image],
		(int) target_left,
		(int) target_top,
		255	/* Opacity */
	) ? TRUE : FALSE;
}

/**
@brief	Save image to file.

@param	image_number	Index of image to save.
@param	filename	Filename to check
@return	::TRUE if successful.
*/

BOOL T3D_WriteImageToFile (image_index image_number, char * filename)
{
	return FreeImage_Save (T3D_RetrievePictureType(filename), images_list [image_number], filename, 0) ? TRUE : FALSE;
}

IMAGE_FORMAT T3D_RetrievePictureType (char * filename)
{
	IMAGE_FORMAT my_format;

	my_format = FreeImage_GetFileType (filename, 0);

	if (my_format == FIF_UNKNOWN)
		my_format = FreeImage_GetFIFFromFilename (filename);

	return my_format;
}

/**
@brief	Retrieve details of image file.

@param	filename	Filename to check
@param	format	Pointer to ::IMAGE_FORMAT containing the file type of image.
@param	width	Pointer to integer the will contain the width of the image.
@param	height	Pointer to integer the will contain the height of the image.
@param	depth	Pointer to integer the will contain the bit depth of the image.
@return	::TRUE if successful.
*/

BOOL T3D_RetrievePictureDetails (char * filename, IMAGE_FORMAT * format, unsigned int * width, unsigned int * height, unsigned int * depth)
{
	IMAGE_FORMAT filetype;
	FIBITMAP * bitmap;

	filetype = T3D_RetrievePictureType (filename);

	#ifdef HAVE_FIF_LOAD_NO_PIXELS
		bitmap = FreeImage_Load (filetype, filename, FIF_LOAD_NOPIXELS );
	#else
		bitmap = FreeImage_Load (filetype, filename, 0 );
	#endif

	if (bitmap)
	{
		*width = FreeImage_GetWidth (bitmap);
		*height = FreeImage_GetHeight (bitmap);
		*depth = FreeImage_GetBPP (bitmap);
		*format = filetype;
		FreeImage_Unload (bitmap);
		return TRUE;
	}
	else
		return FALSE;
}

/**

Add a new FreeImage bitmap to our list of targets.

@return	Pointer to a FreeImage bitmap pointer to the newly added list entry.
*/

FIBITMAP ** T3D_AddImageToList (void)
{
	++current_image;

	if (current_image == 1)
		images_list = (FIBITMAP **) malloc (sizeof (FIBITMAP *));
	else
		images_list = (FIBITMAP **) realloc (images_list, current_image * sizeof (FIBITMAP *));

	return &images_list [current_image - 1];

}


/**

Handle errors thrown by FreeImage and use them to set the tools23d last error.

@param  fif Format of image currently being processed.
@param  message FreeImage error message.
*/

void T3D_ImageErrorHandler(IMAGE_FORMAT fif, const char *message)
{
    T3D_SetLastError ("FreeImage error : %s", (char *) message);
}
